<?php
#
# dmBridge: a data access framework for CONTENTdm(R)
#
# Copyright © 2009, 2010, 2011 Board of Regents of the Nevada System of Higher
# Education, on behalf of the University of Nevada, Las Vegas
#

/**
 * @author Alex Dolski <alex.dolski@unlv.edu>
 * @license http://www.opensource.org/licenses/mit-license.php
 */
class DMHTTPClient {

	/** @var cURL handle */
	private $curl_handle;
	/** @var boolean */
	private $follow_redirects = true;
	/** @var DMHTTPRequest */
	private $request;
	/** @var int (seconds) */
	private $timeout = 10;

	/**
	 * @param DMHTTPRequest request 
	 */
	public function __construct(DMHTTPRequest $request) {
		$this->setRequest($request);
	}

	private function getCURL() {
		if (!$this->curl_handle) {
			$this->curl_handle = curl_init();
			curl_setopt($this->curl_handle, CURLOPT_HTTP_VERSION,
				CURL_HTTP_VERSION_1_1);
			curl_setopt($this->getCURL(), CURLOPT_RETURNTRANSFER, true);
			curl_setopt($this->getCURL(), CURLINFO_HEADER_OUT, true);
		}
		global $cookiearr;
		if (!is_array($cookiearr)) {
			$cookiearr = array();
		}
		global $curl_handle;
		$curl_handle = $this->curl_handle;
		return $this->curl_handle;
	}

	/**
	 * @return boolean
	 */
	public function isFollowingRedirects() {
		return $this->follow_redirects;
	}

	/**
	 * @param boolean bool
	 */
	public function setFollowRedirects($bool) {
		$this->follow_redirects = (bool) $bool;
	}

	/**
	 * @return DMHTTPRequest
	 */
	public function getRequest() {
		return $this->request;
	}

	/**
	 * @param DMHTTPRequest request
	 */
	public function setRequest(DMHTTPRequest $request) {
		$this->request = $request;
	}

	/**
	 * Sends the request.
	 *
	 * @return DMHTTPResponse
	 */
	public function send() {
		// initialize the request
		curl_setopt($this->getCURL(), CURLOPT_URL,
				$this->getRequest()->getURI());
		curl_setopt($this->getCURL(), CURLOPT_TIMEOUT, $this->getTimeout());
		curl_setopt($this->getCURL(), CURLOPT_FOLLOWLOCATION,
				$this->isFollowingRedirects());
		global $cookiearr;
		curl_setopt($this->curl_handle, CURLOPT_HEADERFUNCTION, "read_header");

		switch ($this->getRequest()->getMethod()) {
		case DMHTTPMethod::POST:
			curl_setopt($this->getCURL(), CURLOPT_POST, true);
			curl_setopt($this->getCURL(), CURLOPT_POSTFIELDS,
					$this->getRequest()->getRepresentation()->getFormDataAsString());
			break;
		}

		$headers = array();
		foreach ($this->getRequest()->getHeaders() as $header) {
			$headers[] = $header['key'] . ": " . $header['value'];
		}
		curl_setopt($this->getCURL(), CURLOPT_HTTPHEADER, $headers);
		if ($this->getRequest()->getUsername()) {
			curl_setopt($this->getCURL(), CURLOPT_HTTPAUTH, CURLAUTH_ANY);
			curl_setopt($this->getCURL(), CURLOPT_COOKIESESSION, true);
			curl_setopt($this->getCURL(), CURLOPT_USERPWD,
					sprintf("%s:%s", $this->getRequest()->getUsername(),
							$this->getRequest()->getPassword()));
		}

		// execute the request
		$response = new DMHTTPResponse();
		$rep = new DMHTTPRepresentation();
		$rep->setBody(curl_exec($this->getCURL()));
		$response->setRepresentation($rep);
		$response->setStatusCode(
				curl_getinfo($this->getCURL(), CURLINFO_HTTP_CODE));
		// @todo extract the response message from the response
		//$response->setStatusMessage();
		$redirected_uri = curl_getinfo($this->getCURL(), CURLINFO_EFFECTIVE_URL);
		if ($redirected_uri != $this->getRequest()->getURI()->__toString()) {
			$this->getRequest()->setRedirectedURI(new DMURI($redirected_uri));
		}
		$tmp = curl_getinfo($this->getCURL(), CURLINFO_CONTENT_TYPE);
		$tmp = explode(";", $tmp);
		$type = DMMediaType::getTypeForString($tmp[0]);
		if ($type) {
			$rep->setMediaType($type);
		}

		// extract the response headers from the "headers" string returned by
		// cURL, which are not actually all headers
		$lines = explode("\n",
				curl_getinfo($this->getCURL(), CURLINFO_HEADER_OUT));
		for ($i = 0; $i < count($lines); $i++) {
			$tmp = explode(":", $lines[$i]);
			if (count($tmp) < 2) {
				continue;
			}
			$key = $tmp[0];
			unset($tmp[0]);
			$value = implode(":", $tmp);
			$response->addHeader(trim($key), trim($value));
		}

		// curl does not include the Set-Cookie header in the return value of
		// CURLINFO_HEADER_OUT, so invoke elaborate hack to parse out cookies.
		foreach ($cookiearr as $key => $value) {
			$response->addCookie(trim($key), trim($value));
		}

		return $response;
	}

	/**
	 * @return int
	 */
	public function getTimeout() {
		return $this->timeout;
	}

	/**
	 * @param int timeout
	 */
	public function setTimeout($timeout) {
		$this->timeout = $timeout;
	}

}

/**
 * Overrides curl's cookie jar functionality. Not for public use.
 * @author http://svetlozar.net/page/free-code.html
 */
function read_header($curl_handle, $string) {
	global $location;
	global $cookiearr;
	global $curl_handle;

	if (!is_array($cookiearr)) {
		$cookiearr = array();
	}

	$length = strlen($string);
	if (!strncmp($string, "Set-Cookie:", 11)) {
		$cookiestr = trim(substr($string, 11, -1));
		$cookie = explode(';', $cookiestr);
		$cookie = explode('=', $cookie[0]);
		$cookiename = trim(array_shift($cookie));
		$cookiearr[$cookiename] = trim(implode('=', $cookie));
	}
	$cookie = "";
	if (trim($string) == "") {
		foreach ($cookiearr as $key => $value) {
			$cookie .= "$key=$value; ";
		}
		curl_setopt($curl_handle, CURLOPT_COOKIE, $cookie);
	}
	return $length;
}
